"""
钉钉登录工具 - 使用新版钉钉API (api.dingtalk.com)
支持 OAuth2 流程

该模块实现了钉钉第三方登录的核心功能，通过钉钉开放平台提供的OAuth2接口，
实现用户身份验证和用户信息获取。主要包含获取访问令牌和用户信息两个核心步骤。
"""
import sys
from abc import abstractmethod, ABC

import httpx
from typing import Optional

import self as self
from httpcore import Response

from config import settings


class DingtalkClient:
    """
    钉钉登录客户端 - 使用新版钉钉API
    
    该类封装了与钉钉服务器交互的所有功能，包括获取访问令牌和获取用户信息。
    使用异步HTTP客户端httpx进行网络请求，支持OAuth2授权码流程。
    """

    def __init__(self):
        """
        初始化钉钉客户端
        
        从配置文件中读取应用凭证信息，设置钉钉API的基础URL。
        这些配置信息需要在钉钉开放平台创建应用后获取。
        """
        self.client_id = settings.DINGTALK_APP_KEY       # 钉钉应用的AppKey
        self.client_secret = settings.DINGTALK_APP_SECRET # 钉钉应用的AppSecret
        self.base_url = "https://api.dingtalk.com"       # 钉钉API基础URL

    async def get_user_info_by_code(self, code: str) -> Optional[dict]:
        """
        通过临时授权码获取用户信息
        
        这是钉钉登录的核心方法，通过前端获取的授权码(code)来获取用户信息。
        整个流程分为三步：
        1. 使用code换取access_token和openId
        2. 使用access_token获取用户详细信息
        3. 如果详细信息获取失败，则使用基本的openId信息
        
        Args:
            code (str): 钉钉返回的临时授权码，有效期5分钟，只能使用一次
            
        Returns:
            Optional[dict]: 用户信息字典，包含unionId、openId、nickname等字段
                          如果获取失败则返回None
                          

        """
        try:
            # 1. 从code获取access_token（同时获取openId）
            token_data = await self._get_access_token_by_code(code)
            if not token_data or not token_data.get("accessToken"):
                print("错误：无法获取钉钉Access Token")
                print(f"CLIENT_ID: {self.client_id}")
                return None

            access_token = token_data["accessToken"]
            open_id = token_data.get("openId")
            
            print(f"获取到 openId: {open_id}")
            
            # 2. 尝试获取用户详细信息
            user_info = await self._get_user_info(access_token)
            
            # 3. 如果获取用户信息失败，使用基本信息
            if not user_info and open_id:
                print("无法获取详细用户信息，使用基本信息")
                return {
                    "unionId": open_id,                              # 用户统一ID
                    "openId": open_id,                               # 用户在当前应用的ID
                    "nickname": f"钉钉用户{open_id[-8:]}",            # 默认昵称
                    "avatar_url": None,                              # 头像URL
                    "mobile": None                                   # 手机号
                }
            
            return user_info

        except Exception as e:
            print(f"钉钉登录失败: {e}")
            import traceback
            traceback.print_exc()
            return None

    async def _get_access_token_by_code(self, code: str) -> Optional[dict]:
        """
        通过授权码获取访问令牌和用户标识
        
        第一步OAuth2流程，使用前端传来的临时授权码向钉钉服务器换取访问令牌。
        成功后会返回访问令牌(accessToken)和用户标识(openId)。
        
        Args:
            code (str): 临时授权码，由钉钉前端授权后返回
            
        Returns:
            Optional[dict]: 包含accessToken、openId等信息的字典，失败时返回None
        """


        try:
            # 构造获取访问令牌的API URL
            url = f"{self.base_url}/v1.0/oauth2/userAccessToken"
            # 准备请求参数
            data = {
                "clientId": self.client_id,        # 应用的AppKey
                "clientSecret": self.client_secret, # 应用的AppSecret
                "code": code,                      # 临时授权码
                "grantType": "authorization_code"  # 授权类型，固定值
            }
            
            print(f"[DEBUG] 请求URL: {url}")
            print(f"[DEBUG] 请求参数: clientId={self.client_id}, code={code[:20]}...")

            # 发送POST请求获取访问令牌
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.post(url, json=data)

                # 检查响应状态
                if response.status_code == 200:
                    resp_data = response.json()
                    # 检查返回数据中是否包含访问令牌
                    if "accessToken" in resp_data:
                        print(f"成功获取 accessToken")
                        print(f"返回数据: {resp_data}")
                        # 返回完整数据，包含accessToken和openId
                        return resp_data
                    else:
                        print(f"获取token失败: {resp_data}")
                        return None
                else:
                    print(f"HTTP错误: {response.status_code}, {response.text}")
                    return None
        except Exception as e:
            print(f"获取Access Token异常: {e}")
            return None

    async def _get_user_info(self, access_token: str) -> Optional[dict]:
        """
        使用访问令牌获取用户详细信息
        
        第二步OAuth2流程，使用第一步获取的访问令牌向钉钉服务器请求用户详细信息。
        包括用户昵称、头像、手机号等信息。
        
        Args:
            access_token (str): 访问令牌，由_get_access_token_by_code方法获取
            
        Returns:
            Optional[dict]: 包含用户详细信息的字典，失败时返回None
        """
        try:
            # 构造获取用户信息的API URL
            url = f"{self.base_url}/v1.0/contact/users/me"
            # 设置请求头，必须包含访问令牌
            headers = {
                "x-acs-dingtalk-access-token": access_token
            }

            # 发送GET请求获取用户信息
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.get(url, headers=headers)

                # 检查响应状态
                if response.status_code == 200:
                    data = response.json()
                    print(f"获取到用户信息: {data}")
                    # 整理返回的用户信息数据格式
                    return {
                        "unionId": data.get("unionId") or data.get("openId"),  # 用户统一ID
                        "openId": data.get("openId"),                          # 用户在当前应用的ID
                        "nickname": data.get("nick"),                          # 用户昵称
                        "avatar_url": data.get("avatarUrl"),                   # 用户头像URL
                        "mobile": data.get("mobile")                           # 用户手机号
                    }
                else:
                    print(f"获取用户信息失败: HTTP {response.status_code}, {response.text}")
                    return None
        except Exception as e:
            print(f"获取用户信息异常: {e}")
            return None


# 创建全局实例，方便在其他模块中直接使用
dingtalk_client = DingtalkClient()


















